package com.sse.lb.monitor;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import com.sse.lb.colas.LBManejadorColas;
import com.sse.lb.core.RegistroSistemaCentral;
import com.sse.lb.core.ReglasEnrutamiento;
import com.sse.util.LectorProperties;

/**
 * Implementacion del componente de monitoreo de sistemas centrales registrados.
 * 
 * @author Paul
 * 
 */
public class Monitor implements IMonitor {

	/** Instancia del singleton */
	private static final Monitor instance = new Monitor();

	/**
	 * Mapa para el registro del tiempo de ultima notificacion de supervivencia
	 * de los sistemas centrales.
	 */
	private Map<Long, Long> registroTiempos;

	/** Periodo para la tarea de verificacion */
	private static final long PERIODO = LectorProperties
			.leerPropiedadInt("PeriodoMonitor");

	/**
	 * Tiempo maximo permitido para un sistema central para no informar su
	 * estado.
	 */
	private static final long TIEMPO_MAXIMO = LectorProperties
			.leerPropiedadInt("TimeOutHeartBeat");

	/** Timer para la tarea de monitoreo. */
	private Timer t = new Timer();

	public Monitor() {
		registroTiempos = new ConcurrentHashMap<Long, Long>();
	}

	public static Monitor getInstance() {
		return instance;
	}

	@Override
	public void registrarSistemaCentral(long idSistemaCentral) {
		this.registroTiempos.put(idSistemaCentral, System.currentTimeMillis());

	}

	@Override
	public void eliminarSistemaCentral(long idSistemaCentral) {
		this.registroTiempos.remove(idSistemaCentral);

	}

	@Override
	public void notificarSupervivencia(long idSistemaCentral) {
		this.registroTiempos.put(idSistemaCentral, System.currentTimeMillis());
	}

	@Override
	public void iniciar() {
		t.scheduleAtFixedRate(new MonitorTask(), 10000, PERIODO);
	}

	@Override
	public void detener() {
		this.t.cancel();
	}

	/**
	 * Si un sistema central se considera caido, entonces debe recuperarse todas
	 * las tramas enviadas a el y volver a encolarlas.
	 * 
	 * @param idSistemaCentral
	 */
	public void recuperarSistema(long idSistemaCentral) {

		// Se elimina la referencia del sistema central en las reglas de
		// enrutamiento.
		ReglasEnrutamiento.getInstance().eliminarSistemaCentral(
				idSistemaCentral);

		// Se elimina la referencia del sistema central en el registro.
		RegistroSistemaCentral.getInstance().eliminarSistemaCentral(
				idSistemaCentral);

		// Se remueven todas las tramas asociadas a una
		List<byte[]> tramas = LBManejadorColas.getInstance()
				.removerTodasTramasSistema(idSistemaCentral);

		//
		if (tramas != null) {
			for (byte[] b : tramas) {
				LBManejadorColas.getInstance()
						.encolarTramaAsincronaPrioridad(b);
			}
		}
	}

	/**
	 * Tarea programada para verificar el estado de los sistemas centrales.
	 * 
	 * @author Paul
	 * 
	 */
	private class MonitorTask extends TimerTask {

		@Override
		public void run() {

			for (Entry<Long, Long> entry : instance.registroTiempos.entrySet()) {

				long tiempoActual = System.currentTimeMillis();
				// SI se ha superado el tiempo maximo de inactividad en un
				// sistema central ...
				if ((tiempoActual - entry.getValue()) > TIEMPO_MAXIMO) {
					System.out.println("El sistema central numero: "
							+ entry.getKey()
							+ " ha superado el tiempo maximo de espera.");

					eliminarSistemaCentral(entry.getKey());
					recuperarSistema(entry.getKey());
				}

			}

		}

	}

}
